{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# pythreejs: 3D rendering in the browser \n",
    "\n",
    "## A Jupyter - threejs bridge\n",
    "\n",
    "## https://github.com/jupyter-widgets/pythreejs\n",
    "\n",
    "\n",
    "Pythreejs is a jupyter interactive widget bringing fast WebGL 3d visualization to the Jupyter notebook.\n",
    "\n",
    "- Originally authored by Jason Grout, currently maintained by Vidar Tonaas Fauske\n",
    "- BSD Licensed\n",
    "\n",
    "Pythreejs is *not* a 3d plotting library, it only exposes the threejs scene objects to the Jupyter kernel.\n",
    "\n",
    "**Installation:**\n",
    "\n",
    "```bash\n",
    "conda install -c conda-forge pythreejs\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## This notebook based on [Examples.ipynb](https://github.com/jupyter-widgets/pythreejs/blob/master/examples/Examples.ipynb) \n",
    "\n",
    "One of the many example notebooks at https://github.com/jupyter-widgets/pythreejs/tree/master/examples"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pythreejs import *\n",
    "import numpy as np\n",
    "from IPython.display import display\n",
    "from ipywidgets import HTML, Text, Output, VBox\n",
    "from traitlets import link, dlink"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Simple sphere"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ball = Mesh(geometry=SphereGeometry(radius=1), \n",
    "            material=MeshLambertMaterial(color='red'),\n",
    "            position=[2, 1, 0])\n",
    "\n",
    "c = PerspectiveCamera(position=[0, 5, 5], up=[0, 1, 0],\n",
    "                      children=[DirectionalLight(color='white', position=[3, 5, 1], intensity=0.5)])\n",
    "\n",
    "scene = Scene(children=[ball, c, AmbientLight(color='#777777')])\n",
    "\n",
    "renderer = Renderer(camera=c, \n",
    "                    scene=scene, \n",
    "                    controls=[OrbitControls(controlling=c)])\n",
    "display(renderer)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Properties of the ball can be updated, with changes reflected immediately in the visualization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ball.scale = (0.5,) * 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time, math\n",
    "ball.material.color = '#4400dd'\n",
    "for i in range(1, 150, 2):\n",
    "    ball.scale = (i / 100.,) * 3\n",
    "    ball.position = [math.cos(i / 10.), math.sin(i / 50.), i / 100.]\n",
    "    time.sleep(.05)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Clickable Surface"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Generate surface data:\n",
    "view_width = 600\n",
    "view_height = 400\n",
    "nx, ny = (20, 20)\n",
    "xmax=1\n",
    "x = np.linspace(-xmax, xmax, nx)\n",
    "y = np.linspace(-xmax, xmax, ny)\n",
    "xx, yy = np.meshgrid(x, y)\n",
    "z = xx ** 2 - yy ** 2\n",
    "#z[6,1] = float('nan')\n",
    "\n",
    "\n",
    "# Generate scene objects from data:\n",
    "surf_g = SurfaceGeometry(z=list(z[::-1].flat), \n",
    "                         width=2 * xmax,\n",
    "                         height=2 * xmax,\n",
    "                         width_segments=nx - 1,\n",
    "                         height_segments=ny - 1)\n",
    "\n",
    "surf = Mesh(geometry=surf_g,\n",
    "            material=MeshLambertMaterial(map=height_texture(z[::-1], 'YlGnBu_r')))\n",
    "\n",
    "surfgrid = SurfaceGrid(geometry=surf_g, material=LineBasicMaterial(color='black'),\n",
    "                       position=[0, 0, 1e-2])  # Avoid overlap by lifting grid slightly\n",
    "\n",
    "# Set up picking bojects:\n",
    "hover_point = Mesh(geometry=SphereGeometry(radius=0.05),\n",
    "                   material=MeshLambertMaterial(color='green'))\n",
    "\n",
    "click_picker = Picker(controlling=surf, event='dblclick')\n",
    "hover_picker = Picker(controlling=surf, event='mousemove')\n",
    "\n",
    "# Set up scene:\n",
    "key_light = DirectionalLight(color='white', position=[3, 5, 1], intensity=0.4)\n",
    "c = PerspectiveCamera(position=[0, 3, 3], up=[0, 0, 1], aspect=view_width / view_height,\n",
    "                      children=[key_light])\n",
    "\n",
    "scene = Scene(children=[surf, c, surfgrid, hover_point, AmbientLight(intensity=0.8)])\n",
    "\n",
    "renderer = Renderer(camera=c, scene=scene,\n",
    "                    width=view_width, height=view_height,\n",
    "                    controls=[OrbitControls(controlling=c), click_picker, hover_picker])\n",
    "\n",
    "\n",
    "# Set up picking responses:\n",
    "# Add a new marker when double-clicking:\n",
    "out = Output()\n",
    "def f(change):\n",
    "    value = change['new']\n",
    "    with out:\n",
    "        print('Clicked on %s' % (value,))\n",
    "    point = Mesh(geometry=SphereGeometry(radius=0.05), \n",
    "                 material=MeshLambertMaterial(color='hotpink'),\n",
    "                 position=value)\n",
    "    scene.add(point)\n",
    "\n",
    "click_picker.observe(f, names=['point'])\n",
    "\n",
    "# Have marker follow picker point:\n",
    "link((hover_point, 'position'), (hover_picker, 'point'))\n",
    "\n",
    "# Show picker point coordinates as a label:\n",
    "h = HTML()\n",
    "def g(change):\n",
    "    h.value = 'Green point at (%.3f, %.3f, %.3f)' % tuple(change['new'])\n",
    "    h.value += '  Double-click to add marker'\n",
    "g({'new': hover_point.position})\n",
    "hover_picker.observe(g, names=['point'])\n",
    "\n",
    "display(VBox([h, renderer, out]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# when we change the z values of the geometry, we need to also change the height map\n",
    "surf_g.z = list((-z[::-1]).flat)\n",
    "surf.material.map = height_texture(-z[::-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parametric Functions\n",
    "\n",
    "\n",
    "To use the ParametricGeometry class, you need to specify a javascript function as a string. The function should take two parameters that vary between 0 and 1, and return a `new THREE.Vector3(x,y,z)`.\n",
    "\n",
    "If you want to build the surface in Python, you'll need to explicitly construct the vertices and faces and build a basic geometry from the vertices and faces."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f = \"\"\"\n",
    "function f(origu, origv, vec) {\n",
    "    // scale u and v to the ranges I want: [0, 2*pi]\n",
    "    var u = 2*Math.PI*origu;\n",
    "    var v = 2*Math.PI*origv;\n",
    "\n",
    "    vec.x = Math.sin(u);\n",
    "    vec.y = Math.cos(v);\n",
    "    vec.z = Math.cos(u+v);\n",
    "}\n",
    "\"\"\"\n",
    "surf_g = ParametricGeometry(func=f, slices=16, stacks=16);\n",
    "\n",
    "surf = Mesh(geometry=surf_g, material=MeshLambertMaterial(color='green', side='FrontSide'))\n",
    "surf2 = Mesh(geometry=surf_g, material=MeshLambertMaterial(color='yellow', side='BackSide'))\n",
    "c = PerspectiveCamera(position=[5, 5, 3], up=[0, 0, 1],\n",
    "                      children=[DirectionalLight(color='white',\n",
    "                                                 position=[3, 5, 1],\n",
    "                                                 intensity=0.6)])\n",
    "scene = Scene(children=[surf, surf2, c, AmbientLight(intensity=0.5)])\n",
    "renderer = Renderer(camera=c, scene=scene, controls=[OrbitControls(controlling=c)], width=400, height=400)\n",
    "display(renderer)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "widgets-tutorial",
   "language": "python",
   "name": "widgets-tutorial"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.5"
  },
  "widgets": {
   "state": {
    "01b59842f123469f8518ef01a0a3dc2b": {
     "views": [
      {
       "cell_index": 6
      }
     ]
    },
    "9ad3421bc8374489a73f356fa90eb605": {
     "views": [
      {
       "cell_index": 3
      }
     ]
    },
    "ddb9e9caabe04004a81c108b889973e7": {
     "views": [
      {
       "cell_index": 10
      }
     ]
    },
    "e372695c70254a63af0d6f485dd1f3a3": {
     "views": [
      {
       "cell_index": 3
      }
     ]
    }
   },
   "version": "2.0.0-dev"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
